iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Modern Web

Medusa.js 石化我的心系列 第 25

Day25 進階導讀 - 了解怎麼製作第三方履約

  • 分享至 

  • xImage
  •  

在 Medusa 裡有一個 Fulfillment Module(履約模組),它負責管理「訂單要怎麼出貨、建立出貨紀錄」這些核心概念。

但是,實際跟 第三方物流/出貨系統(像黑貓宅急便、DHL、UPS...) 溝通的,不是 Fulfillment Module 自己,而是 Fulfillment Module Provider(履約供應商提供者)

在 Day12 我們有玩玩看我們的履約了,但是關於到貨卻是我們在設定的,如果是真正的電商網站,運送狀態應該是由第三方運貨公司在進行提供的。

所以今天我帶各位導讀 MedusaJS 製作第三方履約模組的方式。

必要:匯入類別

如果要製作第三方履約,就需要繼承AbstractFulfillmentProviderService類別,實作裡面的函式。

import { AbstractFulfillmentProviderService } from "@medusajs/framework/utils"

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // TODO implement methods
}

export default MyFulfillmentProviderService

必要:dentifier 識別碼

一個電商也不一定會只有一個運輸商,所以我們每一個運輸商都要給一個dentifier(識別碼)作為認識。

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  static identifier = "my-fulfillment"

  // ...
}

必要:calculatePrice 運算價格函式

每一個運貨商有不同的價格標準,所以當購物車建立時候,可以將商品或者運輸速度傳送給第三方貨運商進行價格運算

import { CalculateShippingOptionPriceDTO } from "@medusajs/framework/types"

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async calculatePrice(
    optionData: CalculateShippingOptionPriceDTO["optionData"],
    data: CalculateShippingOptionPriceDTO["data"],
    context: CalculateShippingOptionPriceDTO["context"]
  ): Promise<CalculatedShippingOptionPrice> {
    // assuming the client can calculate the price using
    // the third-party service
    const price = await this.client.calculate(data)
    return {
      calculated_amount: price,
      // Update this boolean value based on your logic
      is_calculated_price_tax_inclusive: true,
    }
  }
}

必要:canCalculate  可以計算?

此方法會驗證是否可以在結帳期間計算託運選項的價格。當管理員使用者建立類型的運送選項時,就會執行它。如果這個方法傳回錯誤,因為無法計算運送選項的價格。

為什麼需要?

並不是每個履約商都有「運費試算」功能。

  • 有些只負責出貨,不會幫你算價錢(例如固定運費 $100)。
  • 有些(像 DHL、UPS、黑貓)有 API,可以依照重量、尺寸、地區算出即時運費。
class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async canCalculate(data: CreateShippingOptionDTO): Promise<boolean> {
    // assuming you have a client
    return await this.client.hasRates(data.id)
  }
}
必要:cancelFulfillment  取消履行

當貨物取消運送的時候,就會需要此函式

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async cancelFulfillment(data: Record<string, unknown>): Promise<any> {
    // assuming the client cancels a fulfillment
    // in the third-party service
    const { external_id } = data as {
      external_id: string
    }
    await this.client.cancel(external_id)
  }
}

必要:createFulfillment 創造履行

這個函式名稱會讓人搞混,並不是自動發貨,而是商家按下建立出貨那一刻,此函式會對第三方運貨中心開一紀錄單,用來追蹤運送資訊。

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async createFulfillment(
    data: Record<string, unknown>,
    items: Partial<Omit<FulfillmentItemDTO, "fulfillment">>[],
    order: Partial<FulfillmentOrderDTO> | undefined,
    fulfillment: Partial<Omit<FulfillmentDTO, "provider_id" | "data" | "items">>
  ): Promise<CreateFulfillmentResult> {
    // assuming the client creates a fulfillment
    // in the third-party service
    const externalData = await this.client.create(
      fulfillment,
      items
    )

    return {
      data: {
        ...(fulfillment.data as object || {}),
        ...externalData
      }
    }
  }
}

必要:createReturnFulfillment 創建退貨履約

當為退貨建立出貨時,會使用到此函式。

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async createReturnFulfillment(fulfillment: Record<string, unknown>): Promise<CreateFulfillmentResult> {
    // assuming the client creates a fulfillment for a return
    // in the third-party service
    const externalData = await this.client.createReturn(
      fulfillment
    )

    return {
      data: {
        ...(fulfillment.data as object || {}),
        ...externalData
      }
    }
  }
}

必要:getFulfillmentDocuments 取得履約文件

對已經建立的履約,簡單說就是貨運商資料顯示

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async getFulfillmentDocuments(data: any): Promise<never[]> {
    // assuming the client retrieves documents
    // from a third-party service
    return await this.client.documents(data)
  }
}

必要:getFulfillmentOptions 取得履約選項

這個方法會取得這個履約(fulfillment)供應商所支援的履約選項。
管理員(Admin 使用者)在建立「配送選項」(shipping option)時,就會從這些選項中挑選。

// other imports...
import { FulfillmentOption } from "@medusajs/framework/types"

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async getFulfillmentOptions(): Promise<FulfillmentOption[]> {
    // assuming you have a client
    const services = await this.client.getServices()

    return services.map((service) => ({
      id: service.service_id,
      name: service.name,
      service_code: service.code,
      // can add other relevant data for the provider to later process the shipping option.
    }))
  }
}

必要:getReturnDocuments 取得退貨文件

當顧客要退貨時,有些物流商(第三方供應商)會要求「退貨必須附帶一些文件」,這個方法就是去取得這些文件

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async getReturnDocuments(data: any): Promise<never[]> {
    // assuming the client retrieves documents
    // from a third-party service
    return await this.client.documents(data)
  }
}

必要:getShipmentDocuments 取得裝運文件

當你用第三方物流(例如 DHL、UPS、順豐)出貨時,系統會需要一些文件來隨貨或提供給倉庫/客戶,這個方法就是去取得這些出貨相關文件

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async getShipmentDocuments(data: any): Promise<never[]> {
    // assuming the client retrieves documents
    // from a third-party service
    return await this.client.documents(data)
  }
}

必要:retrieveDocuments 檢索文件

retrieveDocuments 是用來 取得某個 fulfillment 的文件,但不是限定「出貨」或「退貨」,而是依照你傳入的 文件類型 (type) 來決定要拿什麼文件。

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async retrieveDocuments(
    fulfillmentData: any,
    documentType: any
  ): Promise<void> {
    // assuming the client retrieves documents
    // from a third-party service
    return await this.client.documents(
      fulfillmentData,
      documentType
    )
  }
}

必要:validateFulfillmentData 驗證履行數據

當建立一個 shipping method (配送方式)** 的時候,系統會呼叫 validateFulfillmentData 來檢查並整理這個配送方式帶的資料是否有錯誤。

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async validateFulfillmentData(
    optionData: any,
    data: any,
    context: any
  ): Promise<any> {
    // assuming your client retrieves an ID from the
    // third-party service
    const externalId = await this.client.getId()

    return {
      ...data,
      externalId
    }
  }
}

必要:validateOption 驗證履約選項

先來解釋以下 配送選項 與 配送方法 有什麼差別。

  • 配送方法:根據所選的配送選項,實際加到購物車(cart)或訂單(order)上的配送記錄。它代表這筆訂單最終會用哪一個配送選項來出貨。
  • 配送選項:配送選項是由商店管理員在後台設定的具體配送方式,例如「標準運送」、「快遞運送」等。

所以驗證履約選項也是當管理者建立履約選項時,系統自動帶入驗證資料是否正確。

class MyFulfillmentProviderService extends AbstractFulfillmentProviderService {
  // ...
  async validateFulfillmentData(
    optionData: any,
    data: any,
    context: any
  ): Promise<any> {
    // assuming your client retrieves an ID from the
    // third-party service
    const externalId = await this.client.getId()

    return {
      ...data,
      externalId
    }
  }
}

以上函式就是當要建立 第三方履約模組時,必要的函式以及其解釋。

總結與預告

介紹這一些函式,或許可以讓沒有做過電商平台或者已經有做的電商平台了解一般電商對於語出貨商串接嚴格的規範,也讓我漲了一堆知識。

接下來幾天我會輕鬆一點,篇幅可能會比較小。


上一篇
Day24 進階導讀 - 了解怎麼 製作第三方付款模組
下一篇
Day26 進階導讀 - 通路設定 與 運輸設定
系列文
Medusa.js 石化我的心29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言